package com.yami.shop.mystery.common.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.mystery.common.dao.MysteryBoxExtensionMapper;
import com.yami.shop.mystery.common.dao.MysteryBoxOpenRecordMapper;
import com.yami.shop.mystery.common.dao.MysteryBoxUserArkMapper;
import com.yami.shop.mystery.common.dto.MysteryBoxExpandDto;
import com.yami.shop.mystery.common.model.MysteryBoxExtension;
import com.yami.shop.mystery.common.model.MysteryBoxOpenRecord;
import com.yami.shop.mystery.common.model.MysteryBoxOrder;
import com.yami.shop.mystery.common.service.MysteryBoxOpenRecordService;
import com.yami.shop.mystery.common.service.MysteryBoxService;
import com.yami.shop.mystery.common.utils.AliasMethod;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author wxf
 * @date 2023/4/14 16:33
 */
@Service
@Slf4j
@AllArgsConstructor
public class MysteryBoxOpenRecordServiceImpl extends ServiceImpl<MysteryBoxOpenRecordMapper, MysteryBoxOpenRecord> implements MysteryBoxOpenRecordService {

    private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";

    private final RedissonClient redissonClient;

    private final MysteryBoxService mysteryBoxService;

    private final MysteryBoxExtensionMapper mysteryBoxExtensionMapper;

    private final MysteryBoxUserArkMapper mysteryBoxUserArkMapper;

    /**
     *  通过盲盒订单，初始化开盲盒记录
     *
     * @param mysteryBoxOrder
     * @return
     */
    @Override
    public void initMysteryBoxOpenRecord(MysteryBoxOrder mysteryBoxOrder) {

        if (null == mysteryBoxOrder || mysteryBoxOrder.getMysteryBoxId() == null || StringUtils.isBlank(mysteryBoxOrder.getUserId())) {
            return;
        }
        List<MysteryBoxOrder> list = new ArrayList<>();
        list.add(mysteryBoxOrder);
        initMysteryBoxOpenRecord(list);
    }

    /**
     *
     *
     * @param list
     */
    @Override
    public void initMysteryBoxOpenRecord(List<MysteryBoxOrder> list) {

        if (ObjectUtils.isEmpty(list)) {
            return;
        }
        // 1、按盲盒id分组
        Map<Long, List<MysteryBoxOrder>> openMap = list.stream().collect(Collectors.groupingBy(MysteryBoxOrder::getMysteryBoxId));
        for (Map.Entry<Long, List<MysteryBoxOrder>> entry : openMap.entrySet()) {
            initOpenRecordList(entry.getValue(), entry.getKey());
        }
    }

    /**
     *  2、判断 盲盒id是否被锁，被锁直接返回，未被锁，先锁盲盒
     *
     * @param list
     * @param mysteryBoxId
     */
    private void initOpenRecordList(List<MysteryBoxOrder> list, Long mysteryBoxId) {
        // 2、判断 盲盒id是否被锁，被锁直接返回，未被锁，先锁盲盒
        String key = Constant.OPEN_MYSTERY_BOX_PREFIX + "_" + mysteryBoxId;
        Long cacheStocks = RedisUtil.getLongValue(key);
        if (cacheStocks != null) {
            log.info("正在分配盲盒:" + mysteryBoxId + "中,稍后再处理");
            // 库存不足
            return;
        }

        // 加锁，防止缓存击穿
        RLock rLock = redissonClient.getLock(REDISSON_LOCK_PREFIX + ":openMysteryBox_" + mysteryBoxId);
        try {
            int lockWait = 10;
            if (rLock.tryLock(lockWait, lockWait, TimeUnit.SECONDS)) {
                // 再获取一遍缓存
                cacheStocks = RedisUtil.getLongValue(key);
                if (cacheStocks != null) {
                    log.info("正在分配盲盒:" + mysteryBoxId + "中,稍后再处理");
                    // 库存不足
                    return;
                }
                lockMysteryBox(key, mysteryBoxId, list);
            }
        } catch (InterruptedException e) {
            log.error("InterruptedException:", e);
        } finally {
            try {
                if (rLock.isLocked()) {
                    rLock.unlock();
                }
            } catch (Exception e) {
                log.error("Exception:", e);
            }
        }

    }

    /**
     *  3、查数据库，看该用户是否已经开盒完成，如果完成，跳过；没有完成，校验开盒数量是否正确
     *
     * @param key
     * @param mysteryBoxId
     * @param list
     */
    private void lockMysteryBox(String key, Long mysteryBoxId, List<MysteryBoxOrder> list) {
        RedisUtil.setLongValue(key, 1L, 60);
        try {
            for (MysteryBoxOrder mysteryBoxOrder : list) {
                // 校验当前数据库开盒数量是否大于本次开盒数，大于开盒，开盒，小于不开
                Long count = mysteryBoxUserArkMapper.countInitOpenRecord(mysteryBoxId, mysteryBoxOrder.getUserId());
                if ((ObjectUtil.isEmpty(count) ? 0 : count) - mysteryBoxOrder.getProdCount() < 0) {
                    continue;
                }
                openMysteryBox(mysteryBoxOrder);
            }
        } catch (Exception e) {
            log.error("盲盒初始化出错：", e);
        } finally {
            RedisUtil.del(key);
        }
    }

    /**
     *  初始化盲盒对象
     *
     * @param mysteryBoxOrder
     */
    private void openMysteryBox(MysteryBoxOrder mysteryBoxOrder) {

        Long mysteryBoxId = mysteryBoxOrder.getMysteryBoxId();
        String userId = mysteryBoxOrder.getUserId();
        try {
            for (int i=0; i < mysteryBoxOrder.getProdCount(); i++) {
                openSingleMysteryBox(mysteryBoxId, userId);
            }
        } catch (Exception e) {
            log.error("盲盒初始化出错：", e);
        }
    }

    /**
     *  单个盲盒初始化方法
     *
     * @param mysteryBoxId
     * @param userId
     * @return
     */
    private void openSingleMysteryBox(Long mysteryBoxId, String userId) {

        log.info("获取盲盒关联商品Bid为:{}", mysteryBoxId);
        List<MysteryBoxExpandDto> list = mysteryBoxService.getBoxProdsByPid(mysteryBoxId);
        log.info("获取盲盒关联商品个数为:{}", list.size());
        if(list.isEmpty()){
            log.error("获取盲盒关联商品为空");
            return;
        }

        List<MysteryBoxOpenRecord> openedMysteryOutOfRecords = list(Wrappers.lambdaQuery(MysteryBoxOpenRecord.class)
                .eq(MysteryBoxOpenRecord::getBid, mysteryBoxId).select(MysteryBoxOpenRecord::getPid,MysteryBoxOpenRecord::getUid));
        log.info("所有的【已初始化】开箱记录数:{}", openedMysteryOutOfRecords.size());

        List<MysteryBoxExpandDto> openProds = new ArrayList<>();
        if (ObjectUtils.isNotEmpty(openedMysteryOutOfRecords)) {
            // 统计所有已经开过盲盒的商品和个数，按商品汇总
            Map<Long, List<MysteryBoxOpenRecord>> collection = openedMysteryOutOfRecords.stream().collect(Collectors.groupingBy(MysteryBoxOpenRecord::getPid));
            // 校验盲盒商品抽中次数和库存，如果某商品抽中次数已达标或者商品库存消耗完，该商品退出
            for (MysteryBoxExpandDto expand : list) {
                if (expand.getStock() <= 0) { // 库存不足，直接跳过
                    continue;
                }
                List<MysteryBoxOpenRecord> records = collection.get(expand.getPid());
                if (ObjectUtils.isNotEmpty(records)) {
                    // 过滤该用户的次数
                    records = records.stream().filter(a -> a.getUid().equals(userId)).collect(Collectors.toList());
                    Integer hits = records == null ? 0 : records.size();
                    if (expand.getHits() != 0 && expand.getHits() <= hits) {
                        continue;
                    }
                }
                openProds.add(expand);
            }
        } else {
            openProds =  list;
        }

        List<Double> probList = openProds.stream().map(MysteryBoxExpandDto::getProb).collect(Collectors.toList());
        log.info("盲盒商品概率probList====={}", probList.toString());
        int index = new AliasMethod(probList).next();
        MysteryBoxExpandDto hitDaw = openProds.get(index);
        log.info("盲盒开出====={}，{}", index, hitDaw.getName());

        //减盲盒商品库存
        MysteryBoxExtension extension = new MysteryBoxExtension();
        extension.setStocks(1);
        extension.setId(hitDaw.getId());
        mysteryBoxExtensionMapper.expendStocks(extension);

        // 补录待开启开箱记录
        MysteryBoxOpenRecord record = new MysteryBoxOpenRecord(userId, mysteryBoxId, hitDaw.getPid(), null, new Date(), 0);
        save(record);

    }
}
